﻿namespace Legend.Web
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Web;

    ///<summary>
    /// Handles parsing of and rendering of query strings.
    ///</summary>
    public class QueryStringConverter
    {
        ///<summary>
        /// Parses the specified query.
        ///</summary>
        ///<param name="query">The query to parse.</param>
        ///<returns>The parsed query as a dictionary.</returns>
        /// <exception cref="ArgumentNullException">The query is null.</exception>
        public IDictionary<string, IEnumerable<string>> Parse(string query)
        {
            Require.ThatArgument(query, "query").IsNotNull();

            var parsedQuery = from keyAndValue in Split(query, '&', ';')
                              let keyValuePair = ParseKeyValuePair(keyAndValue)
                              group keyValuePair by keyValuePair.Key into groupedPairs
                              select new
                                     {
                                         Key = Decode(groupedPairs.Key),
                                         Values = from pair in groupedPairs
                                                  from splitValue in Split(pair.Value, ',')
                                                  select Decode(splitValue)
                                     };

            return parsedQuery.ToDictionary(
                x => x.Key, 
                x => x.Values.ToArray().AsEnumerable());
        }

        /// <summary>
        /// Gets a string of the specified query.
        /// </summary>
        /// <param name="query">The query to write as a string.</param>
        /// <returns>A string representing the query.</returns>
        public string ToString(IDictionary<string, IEnumerable<string>> query)
        {
            var result = new StringWriter();

            this.Render(result, query);

            return result.GetStringBuilder().ToString();
        }

        /// <summary>
        /// Renders the specified query to the writer.
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="query"></param>
        public void Render(TextWriter writer, IDictionary<string, IEnumerable<string>> query)
        {
            Require.ThatArgument(writer, "writer").IsNotNull();
            Require.ThatArgument(query, "query").IsNotNull();

            var keyValuePairs = from entry in query
                                from value in entry.Value.DefaultIfEmpty(string.Empty)
                                select new {entry.Key, Value = value};

            var isFirst = true;
            foreach (var keyValuePair in keyValuePairs)
            {
                if (!isFirst)
                {
                    writer.Write("&");
                }
                else
                {
                    isFirst = false;
                }

                writer.Write(Encode(keyValuePair.Key));
                writer.Write("=");
                writer.Write(Encode(keyValuePair.Value));
            }
        }

        private static IEnumerable<string> Split(string value, params char[] splitCharacters)
        {
            return value.Split(splitCharacters, StringSplitOptions.RemoveEmptyEntries);
        }

        private static KeyValuePair<string, string> ParseKeyValuePair(string valuePair)
        {
            var split = valuePair.Split('=');

            var key = split[0];
            var value = split.Length > 1 ? split[1] : string.Empty;

            return new KeyValuePair<string, string>(key, value);
        }

        private static string Decode(string value)
        {
            return HttpUtility.UrlDecode(value);
        }

        private static string Encode(string value)
        {
            return HttpUtility.UrlEncode(value);
        }
    }
}